package com.dny.asmtop.op;

import com.dny.asmtop.ASMMethodUtils;
import com.dny.asmtop.Command;
import com.dny.asmtop.MethodContext;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.GeneratorAdapter;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static java.lang.String.format;
import static jdk.internal.org.objectweb.asm.Type.getType;

/**
 * Created by jlutt on 2018-01-17.
 * 调用外部类静态方法
 *
 * @author jlutt
 */
public class CommandCallStatic implements Command {

  /**
   * 调用方
   */
  private final Class<?> owner;

  /**
   * 调用方法名
   */
  private final String methodName;

  /**
   * 方法参数
   */
  private final List<Command> arguments = new ArrayList<>();

  public CommandCallStatic(Class<?> owner, String methodName, Command... arguments) {
    this.owner = owner;
    this.methodName = methodName;
    this.arguments.addAll(Arrays.asList(arguments));
  }

  @Override
  public Type type(MethodContext context) {
    List<Class<?>> argumentClasses = new ArrayList<>();
    List<Type> argumentTypes = new ArrayList<>();
    for (Command argument : arguments) {
      argumentTypes.add(argument.type(context));
      argumentClasses.add(ASMMethodUtils.getJavaType(context.getClassLoader(), argument.type(context)));
    }

    Class<?>[] arguments = argumentClasses.toArray(new Class<?>[]{});

    Type returnType;
    try {
      Method method = owner.getMethod(methodName, arguments);
      Class<?> returnClass = method.getReturnType();
      returnType = getType(returnClass);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(format("No static method %s.%s(%s). %s",
          owner.getName(),
          methodName,
          (!argumentClasses.isEmpty() ? ASMMethodUtils.argsToString(argumentClasses) : ""),
          ASMMethodUtils.exceptionInGeneratedClass(context)));
    }

    return returnType;
  }

  @Override
  public Type generator(MethodContext context) {
    List<Class<?>> argumentClasses = new ArrayList<>();
    List<Type> argumentTypes = new ArrayList<>();
    for (Command argument : arguments) {
      argument.generator(context);
      argumentTypes.add(argument.type(context));
      if (argument.type(context).equals(getType(Object[].class))) {
        argumentClasses.add(Object[].class);
      } else {
        argumentClasses.add(ASMMethodUtils.getJavaType(context.getClassLoader(), argument.type(context)));
      }
    }

    Class<?>[] arguments = argumentClasses.toArray(new Class<?>[]{});
    Type returnType;
    Method method;
    try {
      Class<?> ownerJavaType = ASMMethodUtils.getJavaType(context.getClassLoader(), Type.getType(owner));
      method = ownerJavaType.getMethod(methodName, arguments);
      Class<?> returnClass = method.getReturnType();
      returnType = getType(returnClass);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(format("No static method %s.%s(%s). %s",
          owner.getName(),
          methodName,
          (!argumentClasses.isEmpty() ? ASMMethodUtils.argsToString(argumentClasses) : ""),
          ASMMethodUtils.exceptionInGeneratedClass(context)));

    }
    GeneratorAdapter g = context.getGeneratorAdapter();
    g.invokeStatic(Type.getType(owner), jdk.internal.org.objectweb.asm.commons.Method.getMethod(method));
    return returnType;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    CommandCallStatic that = (CommandCallStatic) o;
    return Objects.equals(owner, that.owner) &&
        Objects.equals(methodName, that.methodName) &&
        Objects.equals(arguments, that.arguments);
  }

  @Override
  public int hashCode() {
    return Objects.hash(owner, methodName, arguments);
  }
}
